%%% $Id$
%%%---------------------------------------------------------------------
%%% Copyright (c) 2008, Vance Shipley
%%% @author Vance Shipley <vances@motivity.ca>
%%% @end
%%%
%%% All rights reserved.
%%% 
%%% Redistribution and use in source and binary forms, with or without
%%% modification, are permitted provided that the following conditions
%%% are met:
%%% 
%%%    - Redistributions of source code must retain the above copyright
%%%      notice, this list of conditions and the following disclaimer.
%%%    - Redistributions in binary form must reproduce the above copyright
%%%      notice, this list of conditions and the following disclaimer in
%%%      the documentation and/or other materials provided with the 
%%%      distribution.
%%%    - Neither the name of Motivity Telecom  nor the names of its
%%%      contributors may be used to endorse or promote products derived
%%%      from this software without specific prior written permission.
%%%
%%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
%%% "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
%%% LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
%%% A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
%%% OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
%%% SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
%%% LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
%%% DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
%%% THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
%%% (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
%%% OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
%%%
%%%---------------------------------------------------------------------
%%% @doc This library module implements a CODEC for the DVD-Video
%%% 		IFO files.
%%%
-module(dvd_ifo).
-copyright('Copyright (c) 2008 Vance Shipley').
-author('vances@motivity.ca').
-vsn('$Revision$').

-export([video_manager/1, video_title_set/1]).
-export([video_attributes/1, audio_attributes/1,
			program_chain/1, program_chain/2,
			menu_program_chain_table/1, menu_program_chain_table/2,
			table_of_titles/1, table_of_titles/2,
			table_of_titles_and_chapters/1, table_of_titles_and_chapters/2,
			user_operations/1, multichannel_extension/1]).

%%% @headerfile "dvd_ifo.hrl"
-include("dvd_ifo.hrl").

%%----------------------------------------------------------------------
%%  The API
%%----------------------------------------------------------------------

%% @spec (Bin::binary()) -> video_manager()
%% @doc Video Manager Information Management Table.
%%
video_manager(<<"DVDVIDEO-VMG", Last_sector_of_set:32, 0:96,
		Last_sector_of_ifo:32, Version:16, Category:32, 
		Number_of_volumes:16, Volume_number:16, Disc_side:8, 0:152, 
		Number_of_title_sets:16, Provider_identifier:32/binary,
		Pos_code:64, 0:192, Last_byte:32, First_play_program_chain:32, 0:448,
		Menu_vob:32, Table_of_titles:32, Menu_program_chain_table:32,
		Parental_management_masks:32, Title_set_attributes:32, Text_data:32,
		Menu_cell_address_table:32, Menu_vobu_address_map:32, 0:32, 0:32, 0:192, 
		Menu_video_attributes:2/binary, Menu_number_of_audio_streams:16,
		Menu_audio_attributes_Bin:64/binary, 0:128,
		Menu_number_of_subpicture_streams:16,
		Menu_subpicture_attributes:6/binary, 0:1312, _/binary>> = _Bin) ->
	MAA_Size = Menu_number_of_audio_streams * 8,
	<<MAA:MAA_Size/binary, _/binary>>
			 = Menu_audio_attributes_Bin,
	Menu_audio_attributes = audio_attributes(MAA),
	#video_manager{last_sector_of_set = Last_sector_of_set,
			last_sector_of_ifo = Last_sector_of_ifo, version = Version,
			category = Category, number_of_volumes = Number_of_volumes,
			volume_number  = Volume_number, disc_side = Disc_side,
			number_of_title_sets = Number_of_title_sets,
			provider_identifier = binary_to_list(Provider_identifier),
			pos_code = Pos_code, last_byte = Last_byte,
			first_play_program_chain = First_play_program_chain,
			menu_vob = Menu_vob, table_of_titles = Table_of_titles,
			menu_program_chain_table = Menu_program_chain_table,
			parental_management_masks = Parental_management_masks,
			title_set_attributes = Title_set_attributes,
			text_data = Text_data,
			menu_cell_address_table = Menu_cell_address_table,
			menu_vobu_address_map = Menu_vobu_address_map,
			menu_video_attributes = video_attributes(Menu_video_attributes),
			menu_number_of_audio_streams = Menu_number_of_audio_streams,
			menu_audio_attributes = Menu_audio_attributes,
			menu_number_of_subpicture_streams = Menu_number_of_subpicture_streams,
			menu_subpicture_attributes =
					subpicture_attributes(Menu_subpicture_attributes)}.

%% @spec (Bin::binary()) -> video_title_set()
%% @doc Video Title Set Management Table.
%%
video_title_set(<<"DVDVIDEO-VTS", Last_sector_of_set:32, 0:96,
		Last_sector_of_ifo:32, Version:16, Category:32, 
		0:16, 0:16, 0:8, 0:152, 0:16, 0:256, 0:64, 0:192,
		Last_byte:32, 0:32, 0:448, Menu_vob:32, Title_vob:32,
		Table_of_titles_and_chapters:32, Title_program_chain_table:32, 
		Menu_program_chain_table:32, Time_map:32, 
		Menu_cell_address_table:32, Menu_vobu_address_map:32, 
		Title_set_cell_address_table:32, 
		Title_set_vobu_address_map:32, 0:192,
		Menu_video_attributes:2/binary,
		Menu_number_of_audio_streams:16, 
		Menu_audio_attributes_Bin:64/binary, 0:128, 
		Menu_number_of_subpicture_streams:16, 
		Menu_subpicture_attributes:6/binary, 0:1312,
		Title_set_video_attributes:2/binary,
		Title_set_number_of_audio_streams:16, 
		Title_set_audio_attributes_Bin:64/binary, 0:128,
		Title_set_number_of_subpicture_streams:16, 
		Title_set_subpicture_attributes_Bin:192/binary, 0:16, 
		Multichannel_extension:192/binary, _/binary>> = _Bin) ->
	MAA_Size = Menu_number_of_audio_streams * 8,
	<<MAA:MAA_Size/binary, _/binary>>
			 = Menu_audio_attributes_Bin,
	Menu_audio_attributes = audio_attributes(MAA),
	TAA_Size = Title_set_number_of_audio_streams * 8,
	<<TAA:TAA_Size/binary, _/binary>>
			 = Title_set_audio_attributes_Bin,
	Title_set_audio_attributes = audio_attributes(TAA),
	TSA_Size = Title_set_number_of_subpicture_streams * 6,
	<<TSA:TSA_Size/binary, _/binary>>
			 = Title_set_subpicture_attributes_Bin,
	Title_set_subpicture_attributes = subpicture_attributes(TSA),
	#video_title_set{last_sector_of_set = Last_sector_of_set,
			last_sector_of_ifo = Last_sector_of_ifo, version = Version,
			category = Category, last_byte = Last_byte,
			menu_vob = Menu_vob, title_vob = Title_vob,
			table_of_titles_and_chapters  = Table_of_titles_and_chapters,
			title_program_chain_table = Title_program_chain_table,
			menu_program_chain_table = Menu_program_chain_table,
			time_map = Time_map,
			menu_cell_address_table = Menu_cell_address_table,
			menu_vobu_address_map = Menu_vobu_address_map,
			title_set_cell_address_table = Title_set_cell_address_table,
			title_set_vobu_address_map = Title_set_vobu_address_map,
			menu_video_attributes = video_attributes(Menu_video_attributes),
			menu_number_of_audio_streams = Menu_number_of_audio_streams,
			menu_audio_attributes = Menu_audio_attributes,
			menu_number_of_subpicture_streams = Menu_number_of_subpicture_streams,
			menu_subpicture_attributes =
					subpicture_attributes(Menu_subpicture_attributes),
			title_set_video_attributes =
					video_attributes(Title_set_video_attributes),
			title_set_number_of_audio_streams =
					Title_set_number_of_audio_streams,
			title_set_audio_attributes = Title_set_audio_attributes,
			title_set_number_of_subpicture_streams =
					Title_set_number_of_subpicture_streams,
			title_set_subpicture_attributes = Title_set_subpicture_attributes,
 			multichannel_extension =
					multichannel_extension(Multichannel_extension)}.

%% @spec (Bin::binary()) -> video_attributes()
%% @doc Video Attributes.
%%
video_attributes(<<Coding_mode:2, Standard:2, Aspect_ratio:2,
		Automatic_pan_scan:1, Automatic_letterbox:1, Cc_line21_field1:1,
		Cc_line21_field2:1, Unknown:1, Bit_rate:1, Picture_size:2,
		Letterboxed:1, Film_mode:1>>) ->
	#video_attributes{coding_mode = Coding_mode,
			standard = Standard, aspect_ratio = Aspect_ratio,
			automatic_pan_scan = Automatic_pan_scan,
			automatic_letterbox = Automatic_letterbox,
			cc_line21_field1 = Cc_line21_field1,
			cc_line21_field2 = Cc_line21_field2, unknown = Unknown,
			bit_rate = Bit_rate, picture_size = Picture_size,
			letterboxed = Letterboxed, film_mode = Film_mode}.

%% @spec (Bin::binary()) -> audio_attributes() | [audio_attributes()]
%% @doc Audio Attributes.
%%
audio_attributes(<<Coding_mode:3, Multichannel_extension:1, Language_type:2,
		Application_mode:2, Quantization_drc:2, Sample_rate:2, Unknown:1,
		Channels:3, Language_code:16, Language_code_extension:8,
		Code_extension:8, 0:8, App_info:1/binary>>) ->
	AA = #audio_attributes{coding_mode = Coding_mode,
			multichannel_extension = Multichannel_extension,
			language_type = Language_type, application_mode = Application_mode,
			quantization_drc = Quantization_drc, sample_rate = Sample_rate,
			unknown = Unknown, channels = Channels,
			language_code = Language_code,
			language_code_extension = Language_code_extension,
			code_extension = Code_extension},
	case Application_mode of
		0 ->
			AA;
 		1 ->
			AA#audio_attributes{karaoke_attributes =
					karaoke_attributes(App_info)};
 		2 ->
			AA#audio_attributes{surround_attributes =
					surround_attributes(App_info)}
	end;
audio_attributes(<<>>) ->
	[];
audio_attributes(Bin) ->
	audio_attributes(Bin, []).
% @hidden
audio_attributes(<<Bin:8/binary, Rest/binary>>, Acc) ->
	audio_attributes(Rest, [audio_attributes(Bin) | Acc]);
audio_attributes(<<>>, Acc) ->
	lists:reverse(Acc).

%% @spec (Bin::binary()) -> karaoke_attributes()
%% @doc Karaoke Attributes.
%%
karaoke_attributes(<<Unknown:1, Channel_assignments:3, Version:2,
		Mc_intro:1, Mode:1>>) ->
	#karaoke_attributes{unknown = Unknown,
			channel_assignments = Channel_assignments, version = Version,
			mc_intro = Mc_intro, mode = Mode}.

%% @spec (Bin::binary()) -> surround_attributes()
%% @doc Surround Attributes.
%%
surround_attributes(<<Unknown1:4, Dolby_encoded:1, Unknown2:3>>) ->
	#surround_attributes{unknown1 = Unknown1, dolby_encoded = Dolby_encoded,
			unknown2 = Unknown2}.

%% @spec (Bin::binary()) -> subpicture_attributes() | [subpicture_attributes()]
%% @doc Subpicture Attributes.
%%
subpicture_attributes(<<Coding_mode:3, 0:3, Language_type:2, 0:8,
		Language_code:16, Language_code_extension:8, Code_extension:8>>) ->
	#subpicture_attributes{coding_mode = Coding_mode,
			language_type = Language_type, language_code = Language_code,
			language_code_extension = Language_code_extension,
			code_extension = Code_extension};
subpicture_attributes(<<>>) ->
	[];
subpicture_attributes(Bin) ->
	subpicture_attributes(Bin, []).
% @hidden
subpicture_attributes(<<Bin:6/binary, Rest/binary>>, Acc) ->
	subpicture_attributes(Rest, [subpicture_attributes(Bin) | Acc]);
subpicture_attributes(<<>>, Acc) ->
	lists:reverse(Acc).

%% @spec (Bin::binary()) -> user_operations()
%% @doc User Operations.
%%
user_operations(<<0:7, Video_pres_mode_change:1,
		Karaoke_audio_pres_mode_change:1, Angle_change:1,
		Subpic_stream_change:1, Audio_stream_change:1, Pause_on:1,
		Still_off:1, Button_select_or_activate:1,
		Resume:1, Chapter_menu_call:1, Angle_menu_call:1,
		Audio_menu_call:1, Subpic_menu_call:1, Root_menu_call:1,
		Title_menu_call:1, Backward_scan:1, Forward_scan:1,
		Next_pg_search:1, Prev_or_top_pg_search:1, Time_or_chapter_search:1,
		Go_up:1, Stop:1, Title_play:1, Chapter_search_or_play:1,
		Title_or_time_play:1>>) ->
	#user_operations{video_pres_mode_change = Video_pres_mode_change,
			karaoke_audio_pres_mode_change = Karaoke_audio_pres_mode_change,
			angle_change = Angle_change,
			subpic_stream_change = Subpic_stream_change,
			audio_stream_change = Audio_stream_change,
			pause_on = Pause_on, still_off = Still_off,
			button_select_or_activate = Button_select_or_activate,
			resume = Resume, chapter_menu_call = Chapter_menu_call,
			angle_menu_call = Angle_menu_call,
			audio_menu_call = Audio_menu_call,
			subpic_menu_call = Subpic_menu_call,
			root_menu_call = Root_menu_call,
			title_menu_call = Title_menu_call,
			backward_scan = Backward_scan,
			forward_scan = Forward_scan,
			next_pg_search = Next_pg_search,
			prev_or_top_pg_search = Prev_or_top_pg_search,
			time_or_chapter_search = Time_or_chapter_search,
			go_up = Go_up, stop = Stop, title_play = Title_play,
			chapter_search_or_play = Chapter_search_or_play,
			title_or_time_play = Title_or_time_play}.

%% @spec (Bin::binary(), Offset::integer()) -> program_chain()
%% @doc Program Chain Information.
%%
program_chain(Bin, Offset) when is_integer(Offset)  ->
	<<_:Offset/binary, Rest/binary>> = Bin,
	program_chain(Rest).

%% @spec (Bin::binary()) -> program_chain()
%% @doc Program Chain Information.
%%
program_chain(<<0:16, Number_of_programs:8, Number_of_cells:8, 
		Playback_time:4/binary, Prohibited_user_operations:4/binary,
		Audio_stream_control:16/binary,
		Subpicture_stream_control:128/binary, Next_program_chain:16,
		Previous_program_chain:16, Go_up_program_chain:16, Still_time:8,
		Playback_mode:8, Palette:64/binary, Commands_offset:16,
		Program_map_offset:16, Cell_playback_offset:16,
		Cell_position_offset:16, Rest/binary>>) ->
	Command_table = command_table(Rest,
			Commands_offset - ?program_chain_size),
	Program_map = case Number_of_programs of
		0 ->
			[];
		Number_of_programs ->
			program_map(Rest,
					Program_map_offset - ?program_chain_size, Number_of_programs)
	end,
	Cell_playback_table = case Number_of_cells of
		0 ->
			[];
		Number_of_cells ->
			cell_playback_table(Rest,
					Cell_playback_offset - ?program_chain_size, Number_of_cells)
	end,
	Cell_position_table = case Number_of_cells of
		0 ->
			[];
		Number_of_cells ->
			cell_position_table(Rest,
					Cell_position_offset - ?program_chain_size, Number_of_cells)
	end,
	#program_chain{number_of_programs = Number_of_programs,
			number_of_cells = Number_of_cells,
			playback_time = Playback_time,
			prohibited_user_operations =
					user_operations(Prohibited_user_operations),
			audio_stream_control = bin_list(16, Audio_stream_control),
			subpicture_stream_control = bin_list(32, Subpicture_stream_control),
			next_program_chain = Next_program_chain,
			previous_program_chain = Previous_program_chain,
			go_up_program_chain = Go_up_program_chain,
			still_time = Still_time, playback_mode = Playback_mode,
			palette = bin_list(32, Palette),
			commands = Command_table, program_map = Program_map,
			cell_playback = Cell_playback_table,
			cell_position = Cell_position_table}.
	
%% @spec (Bin::binary(), Offset::integer()) -> commands()
%% @doc Command Table.
%%
command_table(Bin, Offset) ->
	<<_:Offset/binary, Rest/binary>> = Bin,
	command_table(Rest).

%% @spec (Bin::binary()) -> commands()
%% @doc Command Table.
%%
command_table(<<Number_of_pre_commands:16, Number_of_post_commands:16,
		Number_of_cell_commands:16, _Last_byte:16, Rest/binary>>) ->
		{Pre_commands, Rest1} = commands(Rest, Number_of_pre_commands, []),
		{Post_commands, Rest2} = commands(Rest1, Number_of_post_commands, []),
		{Cell_commands, _Rest3} = commands(Rest2, Number_of_cell_commands, []),
		#command_table{pre_commands = Pre_commands,
			  post_commands = Post_commands, cell_commands = Cell_commands}.
%% @hidden
commands(Bin, 0, Acc) ->
		  {lists:reverse(Acc), Bin};
commands(<<Command:8/binary, Rest/binary>>, N, Acc) ->
		  commands(Rest, N - 1, [Command | Acc]).

%% @spec (Bin::binary(), Offset::integer(), N::integer()) -> [integer()]
%% @doc Program Map.
%%
program_map(Bin, Offset, N) when is_integer(N) ->
	<<_:Offset/binary, Rest/binary>> = Bin,
	program_map(Rest, N, []);
program_map(_Bin, 0, Acc) ->
	lists:reverse(Acc);
program_map(<<Entry_cell_number:8, Rest/binary>>, N, Acc) ->
	program_map(Rest, N - 1, [Entry_cell_number | Acc]).

%% @spec (Bin::binary(), Offset::integer(), N::integer()) -> [integer()]
%% @doc Cell Playback Information Table.
%%
cell_playback_table(Bin, Offset, N) when is_integer(N) ->
	<<_:Offset/binary, Rest/binary>> = Bin,
	cell_playback_table(Rest, N, []);
cell_playback_table(_Bin, 0, Acc) ->
	lists:reverse(Acc);
cell_playback_table(<<Cell_type:2, Block_type:2, PCI:1, Interleaved:1,
		STC:1, DSI:1, 0:1, Restricted:1, 0:6, Still_time:8, Command_number:8,
		Playback_time:32, First_vobu_start:32, First_ilvu_end:32,
		Last_vobu_start:32, Last_vobu_end:32, Rest/binary>>, N, Acc) ->
	CPB = #cell_playback{cell_type = Cell_type, block_type = Block_type,
			seamless_playback_linked_in_pci = PCI, interleaved = Interleaved,
			stc_discontinuity = STC, seamless_angle_linked_in_dsi = DSI,
			restricted = Restricted, still_time = Still_time,
			command_number = Command_number, playback_time = Playback_time,
			first_vobu_start_sector = First_vobu_start,
			first_ilvu_end_sector = First_ilvu_end,
			last_vobu_start_sector = Last_vobu_start,
			last_vobu_end_sector = Last_vobu_end},
	cell_playback_table(Rest, N - 1, [CPB | Acc]).

%% @spec (Bin::binary(), Offset::integer(), N::integer()) -> Position
%% 	Position = {VOB_ID, Cell_ID}
%% 	VOB_ID = integer()
%% 	Cell_ID = integer()
%% @doc Cell Position Information Table.
%%
cell_position_table(Bin, Offset, N) when is_integer(N) ->
	<<_:Offset/binary, Rest/binary>> = Bin,
	cell_position_table(Rest, N, []);
cell_position_table(_Bin, 0, Acc) ->
	lists:reverse(Acc);
cell_position_table(<<VOB_id:16, 0:8, Cell_id:8, Rest/binary>>, N, Acc) ->
	cell_position_table(Rest, N - 1, [{VOB_id, Cell_id} | Acc]).

%% @spec (Bin::binary(), Offset::integer()) -> [title()]
%% @doc Table of Titles.
%%
table_of_titles(Bin, Offset) when is_integer(Offset)  ->
	<<_:Offset/binary, Rest/binary>> = Bin,
	table_of_titles(Rest).

%% @spec (Bin::binary()) -> [table_of_titles()]
%% @equiv table_of_titles(Bin, 0)
%%
table_of_titles(<<Number_of_titles:16, 0:16, Last_byte:32,
		Rest/binary>>) when Number_of_titles * 12 + 8 =:= Last_byte + 1 ->
	table_of_titles(Rest, Number_of_titles, []).
%% @hidden
table_of_titles(_Bin, 0, Acc) ->
	lists:reverse(Acc);
table_of_titles(<<Title_type:8, Number_of_angles:8,
			Number_of_chapters:16, Parental_management_mask:16,
			Title_set_number:8, Title_number:8, Start_sector:32,
			Rest/binary>>, N, Acc) ->
	TT = #title{title_type = Title_type,
			number_of_angles = Number_of_angles,
			number_of_chapters = Number_of_chapters,
			parental_management_mask = Parental_management_mask,
			title_set_number = Title_set_number, title_number = Title_number,
			start_sector = Start_sector},
	table_of_titles(Rest, N - 1, [TT | Acc]).

%% @spec (Bin::binary(), Offset::integer()) -> Table
%% 	Table = [Title]
%% 	Title = [Chapter]
%% 	Chapter = {ProgramChain, Program}
%% 	ProgramChain = integer()
%% 	Program = integer()
%% @doc Table of Titles and Chapters.
%%
table_of_titles_and_chapters(Bin, Offset) ->
	<<_:Offset/binary, Rest/binary>> = Bin,
	table_of_titles_and_chapters(Rest).

%% @spec (Bin::binary()) -> Titles
%% 	Titles = [Chapters]
%% 	Chapters = [Chapter]
%% 	Chapter = {ProgramChain, Program}
%% 	ProgramChain = integer()
%% 	Program = integer()
%% @doc Titles and Chapters.
%%
table_of_titles_and_chapters(<<Number_of_titles:16, 0:16, Last_byte:32,
		Rest/binary>>) ->
	Titles_size = Number_of_titles * 4,
	<<Titles:Titles_size/binary, Rest1/binary>> = Rest,
	Chapters_size = Last_byte + 1 - Titles_size - 8,
	<<Chapters:Chapters_size/binary, _/binary>> = Rest1,
	titles_and_chapters(Titles, Chapters, []).

%% @spec (Titles::binary(), Chapters::binary(), Acc::list()) -> Titles
%% @doc Titles and Chapters.
%%
titles_and_chapters(<<Offset:32, NextOffset:32, Rest/binary>>,
		Chapters, Acc) ->
	Chapter_size = NextOffset - Offset,
	<< TitleChapters:Chapter_size/binary, RestChapters/binary>> = Chapters,
	RestOffsets = <<NextOffset:32, Rest/binary>>,
	titles_and_chapters(RestOffsets, RestChapters,
			[chapters(TitleChapters, []) | Acc]);
titles_and_chapters(<<_Offset:32>>, Chapters, Acc) ->
	lists:reverse([chapters(Chapters, []) | Acc]).

%% @spec (Bin::binary(), Acc::list()) -> [tuple()]
%% @doc Chapters.
%%
chapters(<<Program_chain:16, Program:16, Rest/binary>>, Acc) ->
	chapters(Rest, [{Program_chain, Program} | Acc]);
chapters(<<>>, Acc) ->
	lists:reverse(Acc).

%% @spec (Bin::binary()) -> [language_unit()]
%% @equiv menu_program_chain_table(Bin, 0)
%%
menu_program_chain_table(Bin) when is_binary(Bin) ->
	menu_program_chain_table(Bin, 0).

%% @spec (Bin::binary(), Offset::integer()) -> [language_unit()]
%% @doc Menu Program Chain Table.
%%
menu_program_chain_table(Bin, Offset)
		when is_binary(Bin), is_integer(Offset)  ->
	<<_:Offset/binary, Number_of_language_units:16, 0:16, _Last_byte:32,
			Rest/binary>> = Bin,
	[LU#language_unit{menu_program_chains =  menu_program_chain(Rest,
			LU#language_unit.menu_program_chains - 8)}
			|| LU <- language_unit(Rest, Number_of_language_units, [])].

%% @spec (Bin::binary(), N::integer(), Acc::list()) -> [language_unit()]
%% @doc Language Unit.
%%
language_unit(_Bin, 0, Acc) ->
	lists:reverse(Acc);
language_unit(<<Language_code1:8, Language_code2:8,
		Language_code_extension:8, Exists:8, LU_offset:32,
		Rest/binary>>, N, Acc) ->
	LU = #language_unit{language_code = [Language_code1, Language_code2],
			language_code_extension = Language_code_extension,
			exists = Exists, menu_program_chains = LU_offset},
	language_unit(Rest, N - 1, [LU | Acc]).

%% @spec (Bin::binary(), LU_offset::integer()) -> [program_chain()]
%% @doc Menu Program Chain.
%%
menu_program_chain(Bin, LU_offset) ->
	<<_:LU_offset/binary, NumberOfProgramChains:16, 0:16,
			_LastByte:32, Rest/binary>> = Bin,
	[MPC#menu_program_chain{program_chain = program_chain(Rest,
			MPC#menu_program_chain.program_chain - 8)}
			|| MPC <- menu_program_chain(Rest, NumberOfProgramChains, [])].
% @hidden
menu_program_chain(_Bin, 0, Acc) ->
	lists:reverse(Acc);
menu_program_chain(<<Category:8, 0:8, Mask:16, PGC_offset:32,
		Rest/binary>>, N, Acc) ->
	MPC = #menu_program_chain{category = Category,
			parental_management_mask = Mask,
			program_chain = PGC_offset},
	menu_program_chain(Rest, N - 1, [MPC | Acc]).

%% @spec (Bin::binary()) ->
%% 		multichannel_extension() | [multichannel_extension()]
%% @doc MultiChannel Extension (Karaoke mode).
%%
multichannel_extension(<<0:7, Ach0_guide_melody:1, 0:7, Ach1_guide_melody:1,
		0:4, Ach2_guide_vocal_1:1, Ach2_guide_vocal_2:1, Ach2_guide_melody_1:1,
		Ach2_guide_melody_2:1, 0:4, Ach3_guide_vocal_1:1, Ach3_guide_vocal_2:1,
		Ach3_guide_melody_A:1, Ach3_sound_effect_A:1, 0:4, Ach4_guide_vocal_1:1,
		Ach4_guide_vocal_2:1, Ach4_guide_melody_B:1, Ach4_sound_effect_B:1,
		0:152>>) ->
	#multichannel_extension{ach0_guide_melody = Ach0_guide_melody,
			ach1_guide_melody = Ach1_guide_melody,
			ach2_guide_vocal_1 = Ach2_guide_vocal_1,
			ach2_guide_vocal_2 = Ach2_guide_vocal_2,
			ach2_guide_melody_1 = Ach2_guide_melody_1,
			ach2_guide_melody_2 = Ach2_guide_melody_2,
			ach3_guide_vocal_1 = Ach3_guide_vocal_1,
			ach3_guide_vocal_2 = Ach3_guide_vocal_2,
			ach3_guide_melody_A = Ach3_guide_melody_A,
			ach3_sound_effect_A = Ach3_sound_effect_A,
			ach4_guide_vocal_1 = Ach4_guide_vocal_1,
			ach4_guide_vocal_2 = Ach4_guide_vocal_2,
			ach4_guide_melody_B = Ach4_guide_melody_B,
			ach4_sound_effect_B = Ach4_sound_effect_B};
multichannel_extension(<<>>) ->
	[];
multichannel_extension(Bin) ->
	multichannel_extension(Bin, []).
% @hidden
multichannel_extension(<<Bin:24/binary, Rest/binary>>, Acc) ->
	multichannel_extension(Rest, [multichannel_extension(Bin) | Acc]);
multichannel_extension(<<>>, Acc) ->
	lists:reverse(Acc).

%%----------------------------------------------------------------------
%%  internal functions
%%----------------------------------------------------------------------

%% @hidden
bin_list(16, Bin) ->
		  bin_list_16(Bin, []);
bin_list(32, Bin) ->
		  bin_list_32(Bin, []).
%% @hidden
bin_list_16(<<Element:16>>, Acc) ->
	lists:reverse([Element | Acc]);
bin_list_16(<<Element:16, Rest/binary>>, Acc) ->
	bin_list_16(Rest, [Element | Acc]).
%% @hidden
bin_list_32(<<Element:32>>, Acc) ->
	lists:reverse([Element | Acc]);
bin_list_32(<<Element:32, Rest/binary>>, Acc) ->
	bin_list_32(Rest, [Element | Acc]).

